"""
串口通信协议模块

该模块实现了与舵机通信的底层协议，包括：
- 串口通信的初始化和配置
- 舵机控制命令的发送和接收
- 数据校验和错误处理
"""

import ctypes
import os
import traceback
import serial
import time
import logging
import stat
from typing import Optional, Tuple, Union, List
from .base_protocol import BaseProtocol
from .gpio_protocol import GPIOProtocol


class SerialProtocol:
    """串口通信协议类"""

    # 命令定义
    CMD_HEAD = 0x55  # 命令头
    CMD_SERVO_MOVE = 0x03  # 舵机移动命令
    CMD_SERVO_READ = 0x04  # 舵机读取命令

    def __init__(
        self,
        port: str = "/dev/ttyS0",
        baudrate: int = 115200,
        timeout: float = 0.1,
        log_level: int = logging.INFO,
    ):
        """
        初始化串口通信协议

        Args:
            port: 串口设备路径
            baudrate: 波特率
            timeout: 超时时间(秒)
            log_level: 日志级别
        """
        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(log_level)  # 设置当前模块的日志级别

        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.serial = None
        self.gpio = GPIOProtocol(log_level=log_level)

    def connect(self) -> bool:
        """
        连接到串口设备

        Returns:
            bool: 是否连接成功
        """
        try:
            if not self._check_serial_permission(self.port):
                return False

            self.serial = serial.Serial(
                port=self.port, baudrate=self.baudrate, timeout=self.timeout
            )
            self.serial.reset_input_buffer()
            self.serial.reset_output_buffer()
            self.logger.info(f"成功连接到串口设备: {self.port}")
            return self.gpio.connect()
        except Exception as e:
            self.logger.error(f"连接串口设备失败: {e}")
            return False

    def _check_serial_permission(self, port: str) -> bool:
        """
        检查串口设备权限

        Args:
            port: 串口设备路径

        Returns:
            bool: 是否有权限访问
        """
        try:
            # 检查设备是否存在
            if not os.path.exists(port):
                logging.error(f"串口设备不存在: {port}")
                return False

            # 检查设备权限
            mode = os.stat(port).st_mode
            if not (mode & stat.S_IROTH and mode & stat.S_IWOTH):
                logging.error(f"串口设备权限不足: {port}")
                logging.info("请执行以下命令添加权限:")
                logging.info(f"sudo chmod 666 {port}")
                logging.info("或添加用户到tty组:")
                logging.info("sudo usermod -a -G tty $USER")
                return False

            return True
        except Exception as e:
            logging.error(f"检查串口权限失败: {e}")
            return False

    def disconnect(self) -> None:
        """断开串口连接"""
        if self.serial and self.serial.is_open:
            self.serial.close()
        self.gpio.disconnect()
        self.logger.info("已断开串口连接")

    def port_write(self) -> None:
        """配置单线串口为输出"""
        self.gpio.port_write()

    def port_read(self) -> None:
        """配置单线串口为输入"""
        self.gpio.port_read()

    def port_reset(self) -> None:
        """重置串口连接"""
        time.sleep(0.1)
        self.serial.close()
        self.gpio.port_reset()
        self.serial.open()
        time.sleep(0.1)

    def _checksum(self, buf: bytearray) -> int:
        """
        计算校验和

        Args:
            buf: 数据缓冲区

        Returns:
            校验和值
        """
        sum_val = 0x00
        for b in buf:  # 求和
            sum_val += b
        sum_val = sum_val - 0x55 - 0x55  # 去掉命令开头的两个 0x55
        sum_val = ~sum_val  # 取反
        return sum_val & 0xFF

    def write_command(
        self,
        servo_id: int,
        cmd: int,
        data: List[int],
        retry_count: int = 1,
    ) -> bool:
        """
        写入命令

        Args:
            servo_id: 舵机ID
            cmd: 命令类型
            data: 命令数据
            retry_count: 重试次数

        Returns:
            bool: 是否写入成功
        """
        self.logger.info(f"写入命令: {servo_id}, {cmd}, {data}")
        if not self.serial or not self.serial.is_open:
            self.logger.error("串口未连接")
            return False

        self.port_write()
        for attempt in range(retry_count):
            try:
                # 确保所有数据都在0-255范围内
                data = [d & 0xFF for d in data]

                # 构建命令数据包
                length = 3  # 舵机ID(1) + 数据长度(1) + 命令类型(1)

                if len(data) > 1:
                    length = 7
                elif len(data) > 0:
                    length = 4

                packet = [
                    self.CMD_HEAD,
                    self.CMD_HEAD,
                    servo_id,
                    length & 0xFF,
                    cmd & 0xFF,
                ]

                # 处理数据，将每个数据拆分为低8位和高8位（如果需要）
                if len(data) > 1:  # 确保有成对的数据
                    dat1 = data[0]
                    dat2 = data[1]
                    # 分别添加低8位和高8位
                    packet.extend([dat1, dat1 >> 8])  # 分低8位 高8位 放入缓存
                    packet.extend([dat2, dat2 >> 8])  # 分低8位 高8位 放入缓存
                else:  # 处理单个剩余数据
                    dat1 = data[0]
                    packet.extend([dat1])  # 偏差

                checksum = self._checksum(bytes(packet.copy()))  # 计算校验和
                packet.append(checksum)
                self.logger.info(f"写入命令: {packet.copy()}")
                # 发送数据包
                self.serial.write(bytes(packet))
                return True
            except Exception as e:
                self.logger.warning(
                    f"写入命令失败 (尝试 {attempt + 1}/{retry_count}): {e}"
                )
                self.flush_output()
                if attempt < retry_count - 1:
                    time.sleep(0.1)
                    continue
                return False

    def read_command(self, servo_id: int, cmd: int, retry_count: int = 1) -> bool:
        """
        读取命令响应

        Args:
            servo_id: 舵机ID
            cmd: 命令类型
            retry_count: 重试次数

        Returns:
            bool: 是否读取成功
        """
        self.logger.info(f"读取命令: {servo_id}, {cmd}, {retry_count}")
        if not self.serial or not self.serial.is_open:
            self.logger.error("串口未连接")
            return False

        self.port_write()
        for attempt in range(retry_count):
            try:
                # 构建命令数据包
                packet = bytearray([self.CMD_HEAD, self.CMD_HEAD])  # 帧头
                packet.append(servo_id)
                packet.append(3)  # 指令长度
                packet.append(cmd)  # 指令
                packet.append(self._checksum(packet))  # 校验和
                self.serial.write(packet)  # 发送
                time.sleep(0.00034)
                self.logger.info(f"读取命令发送成功: {packet.copy()}")
                return True
            except Exception as e:
                self.logger.warning(
                    f"读取命令失败 (尝试 {attempt + 1}/{retry_count}): {e}"
                )
                self.flush_output()
                if attempt < retry_count - 1:
                    time.sleep(0.1)
                    continue
                return False

    def flush_input(self) -> None:
        """清空输入缓冲区"""
        if not self.serial or not self.serial.is_open:
            self.logger.error("串口未连接")
            return

        try:
            self.serial.reset_input_buffer()
            self.port_reset()
        except Exception as e:
            self.logger.error(f"清空输入缓冲区失败: {e}")

    def flush_output(self) -> None:
        """清空输出缓冲区"""
        if not self.serial or not self.serial.is_open:
            self.logger.error("串口未连接")
            return

        try:
            self.serial.reset_output_buffer()
            self.port_reset()
        except Exception as e:
            self.logger.error(f"清空输出缓冲区失败: {e}")

    def get_response(
        self, cmd: int, timeout: float = 0.1
    ) -> Optional[Union[int, Tuple[int, int]]]:
        """
        获取响应数据

        Args:
            cmd: 期望的命令类型
            timeout: 超时时间(秒)

        Returns:
            Optional[Union[int, Tuple[int, int]]]:
                - 单字节数据时返回int
                - 双字节数据时返回int
                - 四字节数据时返回Tuple[int, int]
                - 失败时返回None
        """
        if not self.serial or not self.serial.is_open:
            self.logger.error("串口未连接")
            return None

        try:
            self.logger.debug("开始获取响应数据...")
            self._prepare_for_response()
            self.logger.debug(f"准备获取响应数据: {cmd}")
            buffer_length = self.serial.in_waiting
            # 等待数据到达
            start_time = time.time()
            while buffer_length == 0:
                time.sleep(0.001)
                buffer_length = self.serial.in_waiting
                if time.time() - start_time > timeout:
                    self.logger.debug("获取响应数据超时")
                    self.flush_input()
                    break

            self.logger.debug(f"获取响应数据: {buffer_length}")
            return self._parse_response_data(cmd, buffer_length)

        except Exception as e:
            self.logger.error(f"获取响应数据失败: {e}")
            self.logger.debug(f"异常详情: {traceback.format_exc()}")
        finally:
            self.flush_input()
            return None

    def _prepare_for_response(self) -> None:
        """准备接收响应数据"""
        self.flush_input()
        self.port_read()
        time.sleep(0.005)  # 等待接收完毕

    def _parse_response_data(
        self, cmd: int, count: int
    ) -> Optional[Union[int, Tuple[int, int]]]:
        """解析响应数据"""
        self.logger.debug(f"解析响应数据: {cmd} {count}")
        if count == 0:
            self.logger.debug("等待响应超时")
            self.flush_input()
            return None

        recv_data = self.serial.read(count)
        self.logger.debug(f"接收到的原始数据: {[hex(b) for b in recv_data]}")

        if not self._validate_response_header(recv_data, cmd):
            self.logger.debug("数据头或命令类型不匹配")
            self.flush_input()
            return None

        dat_len = recv_data[3]
        self.logger.debug(f"数据长度: {dat_len}")

        try:
            if dat_len == 4:
                return self._parse_single_byte_data(recv_data)
            elif dat_len == 5:
                return self._parse_double_byte_data(recv_data)
            elif dat_len == 7:
                return self._parse_quad_byte_data(recv_data)
            else:
                self.logger.warning(f"不支持的数据长度: {dat_len}")
                self.port_reset()
                self.flush_input()
                return None
        except IndexError as e:
            self.logger.error(f"数据解析错误: {e}")
            self.port_reset()
            self.flush_input()
            return None

    def _validate_response_header(self, data: bytes, cmd: int) -> bool:
        """验证响应数据头"""
        try:
            is_valid = (
                data[0] == self.CMD_HEAD and data[1] == self.CMD_HEAD and data[4] == cmd
            )
            if not is_valid:
                self.logger.debug("数据头或命令类型不匹配")
            return is_valid
        except IndexError:
            self.logger.debug("响应数据长度不足")
            return False

    def _parse_single_byte_data(self, data: bytes) -> int:
        """解析单字节数据"""
        result = data[5]
        self.logger.debug(f"单字节数据: {hex(result)}")
        return result

    def _parse_double_byte_data(self, data: bytes) -> int:
        """解析双字节数据"""
        pos = 0xFFFF & (data[5] | (0xFF00 & (data[6] << 8)))
        result = ctypes.c_int16(pos).value
        self.logger.debug(
            f"双字节数据: 低字节={hex(data[5])}, 高字节={hex(data[6])}, 结果={result}"
        )
        return result

    def _parse_quad_byte_data(self, data: bytes) -> Tuple[int, int]:
        """解析四字节数据"""
        pos1 = 0xFFFF & (data[5] | (0xFF00 & (data[6] << 8)))
        pos2 = 0xFFFF & (data[7] | (0xFF00 & (data[8] << 8)))
        result1 = ctypes.c_int16(pos1).value
        result2 = ctypes.c_int16(pos2).value
        self.logger.debug(f"四字节数据: 第一组={result1}, 第二组={result2}")
        return result1, result2
